#define _GNU_SOURCE 1
#include<t_stdio.h>
#include<t_file.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/poll.h>
#include<fcntl.h>
#include<errno.h>
#define user_limit 5 //最大客户连接数量
#define buffer_size 64
#define fd_limit 65535 //最大文件描述符数量

struct client_data{//创建一个客户地址结构体
    struct sockaddr_in address ; 
    char * write_buf;
    char buf[buffer_size];
};

int setnonblocking (int fd){//将文件描述符改为非阻塞模式
    int old_option = fcntl(fd , F_GETFL);
    int new_option = old_option | O_NONBLOCK;// 添加非阻塞选项
    fcntl(fd , F_SETFL , new_option);//设置
    return old_option;

}

int main(int argc , char *argv[]){
    if(argc <= 2)//如果参数太少
    {
        printf("usage :%s ip_address port_number\n",basename(argv[0]));
        return 1;
    }

    const char * ip = argv[1] ;// 提取ip地址
    int port = atoi(argv[2]); //提取端口号

    struct sockaddr_in address ; //服务器地址
    bzero(&address ,sizeof(address));//清空
    address.sin_family = AF_INET;
    inet_pton(AF_INET , ip ,&address.sin_addr);//设置ip
    address.sin_port = htons(port); //设置端口号

    int listenfd = socket(PF_INET ,SOCK_STREAM , 0);//创建监听套接字
    assert(listenfd >=0);

    int ret = bind(listenfd , (struct sockaddr*)&address , sizeof(address));//绑定
    assert(ret !=-1);

    ret = listen(listenfd ,5);//最多同时监听五个
    assert(ret!=-1);

    //创建user数组，放入多个客户对象，并且使用socket的值可以直接用来索引（作为数组下标）连接对应的client_data对象
    struct client_data * user = malloc(fd_limit * sizeof(struct client_data));
   //为了提高poll性能，限制用户数量
    struct pollfd *fds = malloc(sizeof(struct pollfd) * 6);
    int user_counter = 0;//计算客户连接数量
    int i=0;
    for( i =  1 ; i<=user_limit ; ++i){//对每个fds数据初始化
        fds[i].fd = -1;
        fds[i].events =0;
    }
    //初始化怕poll中第一个数据：监听套接字
    fds[0].fd = listenfd;
    fds[0].events = POLLIN | POLLERR;
    fds[0].revents = 0;

    while(1){
        ret = poll(fds , user_counter+1 , -1);//开始监听
        if(ret <0) {
            printf("poll failed..\n");
            break;
        }

        for ( i =0 ; i <user_counter+1;i++){//每次对整个fds数组进行遍历处理
            if(fds[i].fd==listenfd && (fds[i].revents & POLLIN)){//如果为第一个监听字符且发生可读事件时
                struct sockaddr_in client_address;//创建一个新客户套接字
                socklen_t client_addrlength = sizeof(client_address);
                int connfd = accept(listenfd ,(struct sockaddr*)&client_address ,&client_addrlength );//获取客户端套接字
                if(connfd<0){//连接错误
                    printf("erron is:%dd\n");
                    continue;
                }
                if(user_counter >=user_limit){//用户太多
                    const char * info ="too many users\n";
                    printf("%s\n",info);
                    send(connfd , info ,strlen(info) , 0);//发送错误给客户端
                    close(connfd);
                    continue;
                }
                //对于新连接 ，我们要同时修改fds和users数组，user[connfd]即对应客户端数据
                user_counter++;//客户数量加一
                user[connfd].address = client_address;
                setnonblocking(connfd);//设置为非阻塞模式
                fds[user_counter].fd = connfd;//最新数据放入数组
                fds[user_counter].events = POLLIN | POLLRDHUP | POLLERR;
                fds[user_counter].revents = 0;
                printf("comes a new user , now have %d user\n",user_counter);

            }
            else if(fds[i].revents & POLLERR){//如果当前监听的客户连接出错了
                printf("get an error from %d\n",fds[i].fd);
                char errors[100];
                memset(errors ,'\0' , 100);
                socklen_t length = sizeof (errors);
                if(getsockopt(fds[i].fd ,SOL_SOCKET,SO_ERROR ,&errors, &length) < 0){//获取错误原因
                    printf("get sock option falied\n");
                }
                continue;
            }
            else if(fds[i].revents & POLLIN){//如果当前客户端连接发生可读事件
                int connfd = fds[i].fd;
                memset(user[connfd].buf , '\0' , buffer_size);//将该客户端结构体数据数组清空
                ret = recv(connfd , user[connfd].buf ,buffer_size,0);//接收数据
                if(ret<0){
                    printf("error");
                    }                
                printf("get %d bytes of client data %s from %d \n", ret ,user[connfd].buf , connfd);
                if(ret <0){
                    //如果读操作出错，则关闭连接
                    if(errno != EAGAIN){
                        close(connfd);
                        user[fds[i].fd] = user[fds[user_counter].fd];
                        fds[i] = fds[user_counter];
                        i--;
                        user_counter--;
                    }
                }
                else if(ret ==0){}
                else{
                    //如果接收到了客户数据，则通知其他socket准备写数据
                    int j = 0;
                    for( j = 1 ;j<user_counter+1 ; j++){
                        if(fds[j].fd == connfd){
                            continue;
                        }
                        //改写找到的第一个套接字表
                        fds[j].events |= ~POLLIN;
                        fds[j].events |= POLLOUT;
                        user[fds[j].fd].write_buf = user[connfd].buf;//将数据放入被选中的socket结构中的写缓冲区
                    }
                }
            }
            else if(fds[i].revents & POLLRDHUP){//如果连接关闭
                user[fds[i].fd] = user[fds[user_counter].fd];//当前套接字转换为最后一个套接字，模拟删除该套接字
                close(fds[i].fd);
                fds[i] = fds[user_counter];
                i--;
                user_counter--;//连接减少
                printf("a client left..\n");
            }
            else if(fds[i].revents & POLLOUT){//如果该套接字发生写事件
                int connfd = fds[i].fd;
                if(! user[connfd].write_buf){//如果该套接字写缓冲区没有数据，则直接跳出
                    continue;
                }
                ret =send(connfd , user[connfd].write_buf , strlen(user[connfd].write_buf), 0 );
                user[connfd].write_buf = NULL;//清空数据
                //需要从新注册事件
                fds[i].events |= ~POLLOUT;
                fds[i].events |= POLLIN;
            }

        }


    }
    //free user;
    close(listenfd);
    return 0;
}